《软件方法(下)》第8章2023版连载(04)面向对象的假设
8.2 建模步骤C-1 识别类和属性
8.2.1 面向对象的假设
注意标题中提到的"假设"二字。面向对象就是一种假设,如果不认可“面向对象”的假设,也可以分析系统的核心域知识,只不过用的方法不叫“面向对象方法”,叫“面向过程”、“面向组件”、“面向肥皂”、“面向武德”都行,看你的假设是什么了。
面向对象的思考方式比目前的其他思考方式要好一点,原因不是计算机喜欢面向对象或者面向对象更接近于计算机的底层,而是面向对象的思考方式更能帮助人脑去剖析复杂问题。如果计算机有感情,估计它应该更"喜欢"人类用机器语言直接给它发指令,因为这样自己就不用受累搞什么编译、链接。
正如前文提到的,三角函数更能解决复杂问题,不意味着它比全等三角形、相似三角形更容易掌握。面向对象更能帮助剖析复杂问题,不意味着面向对象的思考方式比其他的思考方式更容易掌握,而且随着你掌握了更强有力的思考工具,更复杂的问题就会扑面而来。这些问题早已存在,只不过之前你没有能力来发现和对付它们——“古人很少死于癌症”。
当使用面向对象的方法来分析系统时,我们引入的第一个假设是:
系统由"对象"这样一种东西构成,对象封装了数据和行为。
严格来说,我们建模的是类(也许叫“基于类的方法”更合适),即对象的“模板”,而不是对象。对象是运行时(或模拟运行时)才产生的。
我们通过抽象思维把具有共同特征的对象集合归纳为"类",对象看作类的实例。归类是人类认知的一种基本技能,其哲学讨论可以追溯到柏拉图的理型论(Theory of Forms)。
我们引入的第二个假设是:
对象在一个"对象空间"中运行,在这个空间中发生的所有事情消耗的时间为零。
图8-18 想象的"对象空间"
您可以认为这个"对象空间"存在于大脑中,也可以把"对象空间"想象成一台存储空间无限大,通信和运算速度无限快且分布在全宇宙的超级计算机。在这个假设下,不用考虑什么硬盘、内存、Cache、加载,只需要聚焦于思考核心域知识。
当然,“时间为零”、“无限快”是不可能的。我们的宇宙,目前因果关系的最快速度是光速。
当前现实中的计算机和网络,要发生一段因果关系,例如,从提交关键词到返回查询结果,时间估计会以秒来计,不过,作为人类的涉众可能对此已经表示满意了。
如果当前的计算机和网络资源能够满足人们对性能的要求,那么设计模型(代码、存储……)和分析模型之间映射会非常直接。
反之,如果出现不可调和的性能问题,设计模型可能会有所调整,例如,添加一些冗余,但这样的调整和具体的核心域知识无关,可以把它们归纳成一些套路,出现相应问题时按照套路调整即可,不需要在分析时考虑这些问题。
“不考虑性能”这一点,可以用来判断你思考的问题是分析问题还是设计问题。
我们可以针对分析模型里的元素,一个一个问,“如果没有它,会怎么样”,如果回答是“会有性能问题”,那么,可以从分析模型中把它删掉。
例如,类图中有一个冗余的类,问“如果没有它,会怎么样”,答“查询可能会慢”——可以删掉。状态机图里有一个状态“Transient”,问“如果没有它,会怎么样”,答“会漏掉某些数据没有持久化”——可以删掉。
但如果回答是“没有它,系统就没法履行自己的责任了,因为要做的系统就是一个持久化框架”,那就不一样了。
分析模型受到设计的污染,很容易导致批量的废话刷工作量,导致没有时间思考应该重点思考的核心域逻辑。当然,正如前文所说,这也可能正是某些人故意寻找的遮羞布。
8.2.2 三种分析类
8.2.2.1 Ivar Jacoson的假设
我们引入的第三个假设是:
系统中存在三种分析类:边界类(Boundary Class)、控制类(Control Class)和实体类(Entity Class)。
这个假设借用了Ivar Jacoson在“Object-Oriented Software Engineering: A Use Case Driven Approach”(Jacobson 1992)中的思想。
在UML模型中,我们可以用Ivar Jacoson建议的构造型(Stereotype)来表示三种分析类,如图8-19。
图8-19 三种分析类的构造型
一些UML工具(如Enterprise Architect、Visual Paradigm)已经内置了这些分析类构造型。如果使用的建模工具没有内置这些构造型,可以自己添加如“<<边界>>”等文字构造型;或者不用构造型区分,通过给类起名"某某接口","某某控制",也有助于了解该类在系统中扮演的角色。这一点,和第3章讲到业务工人、业务实体时的做法是一样的。
在设计工作流,三种分析类可以映射到任何实现架构,包括但不限于MVC、MVP、MVVM、六边形、洋葱型……甚至映射到不做任何分割的“架构”。
图8-20列出了三种分析类的责任、和需求的关系以及命名。
图8-20 分析类的责任、和需求的关系以及命名
图8-21用序列图展示了三种分析类之间的协作。
图8-21 三种分析类在系统中的协作
执行者先把消息发给边界类对象。边界类对象履行它有能力履行的责任,然后把它没有能力履行的责任委托给控制类对象。控制类对象就像总裁办,不做具体工作,只是将责任分解后分配给实体类对象。
分配给实体类对象时,如果某个对象被其他对象组合,应该先分配给组合它的对象,再由该对象分配给它。DDD话语体系中的“聚合(Aggregate)”和这一点类似,本书在后文讲述类关系的章节中会进一步阐述其中差别以及“聚合(Aggregate)”的伪创新。
最后,由边界类对象向外系统反馈信息,完成一个交互回合。